package employee

import (
	"gitee.com/baal1990/gitee.com/baal1990/cmd/server_attence/app/config"
	"gitee.com/baal1990/gitee.com/baal1990/cmd/server_attence/app/dao"
	"gitee.com/baal1990/gitee.com/baal1990/cmd/server_attence/app/entity"
	"gitee.com/baal1990/gitee.com/baal1990/cmd/server_attence/app/global"
	"gitee.com/baal1990/gitee.com/baal1990/pkg/cache"
	"gitee.com/baal1990/gitee.com/baal1990/pkg/crypto"
	"gitee.com/baal1990/gitee.com/baal1990/pkg/model"
	"gitee.com/baal1990/gitee.com/baal1990/pkg/util"
	"github.com/gin-gonic/gin"
	"github.com/gomodule/redigo/redis"
	uuid "github.com/satori/go.uuid"
	"go.uber.org/zap"
	"net/http"
	"strconv"
	"strings"
	"time"
)

//********************************************
//Func  ：查询用户列表，分页查询。
//Method：GET.
//PARAM:role,pageSize,page,mobile,deptId
//Usage ：
//********************************************
func QueryAllEmployees(c *gin.Context) {
	requester := c.MustGet("USER").(*entity.Employee) //请求人
	global.Log.Info("QueryAllEmployees() start @ ", zap.Any("USER", requester))
	pageSize, err1 := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
	if err1 != nil {
		global.Log.Error("QueryAllEmployees input param pagesize error:", zap.Any("error", err1))
		c.JSON(http.StatusOK, model.Fail("请求参数错误"))
		return
	}

	page, err2 := strconv.Atoi(c.DefaultQuery("page", "1"))
	if err2 != nil {
		global.Log.Error("QueryAllEmployees input param page error:", zap.Any("error", err2))
		c.JSON(http.StatusOK, model.Fail("请求参数错误"))
		return
	}

	mobile := c.DefaultQuery("mobile", "")
	if mobile == "" {
		global.Log.Info("QueryAllEmployees all employees without mobile")
		if requester.Role != entity.EEmployeeRole_DeptLeader && requester.Role != entity.EEmployeeRole_Hr {
			global.Log.Error("QueryAllEmployees role error.")
			c.JSON(http.StatusOK, model.Fail("只有Hr和部门主管才能查询非本人信息"))
			return
		}
	}

	//指定部门，单选，若有部门支持多个，逗号分隔
	var deptIdList []int64
	deptIdsStr := c.DefaultQuery("deptId", "") //人员列表
	if deptIdsStr != "" {
		idList := strings.Split(deptIdsStr, ",")
		for _, idstr := range idList {
			id, err := strconv.ParseInt(idstr, 10, 64)
			if err == nil {
				deptIdList = append(deptIdList, id)
			}
		}
	}

	//根据请求人的角色，决定查询角色
	role, err := strconv.Atoi(c.DefaultQuery("role", ""))
	if err != nil {
		global.Log.Info("un special role.", zap.Any("error", err))
		role = entity.EEmployeeRole_Undef //未说明角色
	}
	status := entity.EEmployeeStatus_Normal

	global.Log.Info("QueryAllEmployees() Query param:",
		zap.Any("page", page), zap.Any("pageSize", pageSize), zap.Any("mobile", mobile),
		zap.Any("deptIdList", deptIdList), zap.Any("status", status), zap.Any("role", role))

	total, userList, err := dao.QueryEmployeeList(page, pageSize, mobile, deptIdList,
		status, role)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("查询失败"))
		global.Log.Info("QueryCustomList failed ", zap.Any("error:", err))
		return
	}
	idDeptMap := dao.QueryDepartmentMap()

	type EmployeeList struct { //列表查询输出
		Total int64
		List  []*entity.EmployeeInfo
	}
	output := &EmployeeList{Total: total}
	for _, employee := range userList {
		info := &entity.EmployeeInfo{
			Id:        strconv.FormatInt(employee.Id, 10),
			Name:      employee.Name,                            //姓名（必填）
			Password:  employee.Password,                        //密码（必填）
			Mobile:    employee.Mobile,                          //手机（必填）
			Email:     employee.Email,                           //邮件
			DeptId:    strconv.FormatInt(employee.DeptId, 10),   //所属部门ID（必填）
			LeaderId:  strconv.FormatInt(employee.LeaderId, 10), //主管领导ID（必填）
			CreatedAt: employee.CreatedAt,                       //创建用户时间
			UpdateAt:  employee.UpdateAt,                        //更新用户时间
			Status:    employee.Status,                          //当前员工状态：0正常、1锁定、2离职
			Role:      employee.Role,                            //当前员工角色：0：外包雇员；1：部门总监(司编雇员）；2：HR管理员
			Vendor:    employee.Vendor,                          //厂商信息
			WorkStart: employee.WorkStart,                       //上班时间
			WorkEnd:   employee.WorkEnd,                         //下班时间
		}

		if employee.DeptId > 0 {
			deptInfo, has := idDeptMap[employee.DeptId]
			if has {
				if deptInfo != nil {
					employee.LeaderId = deptInfo.DeptHeadId
					info.LeaderId = strconv.FormatInt(employee.LeaderId, 10)
				}
			} else {
				employee.DeptId = 0 //部门不存在
				info.LeaderId = ""
				info.DeptId = ""
			}
		}
		output.List = append(output.List, info)
	}

	global.Log.Info("QueryAllEmployees success, output:", zap.Any("employee list:", output))
	c.JSON(http.StatusOK, model.SuccessObject("操作成功", output))
}

//********************************************
//Func  ：查询指定用户详情
//Method：GET
//Param：Id操作员ID
//Usage ：
//********************************************
func QueryEmployeeDetail(c *gin.Context) {

	requester := c.MustGet("USER").(*entity.Employee) //请求人
	global.Log.Info("QueryEmployeeDetail() start @ ", zap.Any("USER", requester))

	id, err := strconv.ParseInt(c.DefaultQuery("id", ""), 10, 64)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("未指定用户信息"))
		return
	}

	if requester.Id != id { //请求人和被查询人不一致，那么就要判别一下请求人是否有权限了
		if requester.Role != entity.EEmployeeRole_DeptLeader && requester.Role != entity.EEmployeeRole_Hr {
			global.Log.Error("QueryEmployeeDetail role error.")
			c.JSON(http.StatusOK, model.Fail("员工只能查询自己的详情"))
			return
		}
	}

	mobile := c.DefaultQuery("mobile", "")
	if id <= 0 && mobile == "" {
		global.Log.Error("QueryEmployeeDetail query employee detail failed, Error")
		c.JSON(http.StatusOK, model.Fail("未指定员工信息"))
		return
	}

	employee, err := dao.QueryEmployeeDetail(mobile, id)
	if err != nil {
		global.Log.Error("dao.QueryEmployeeDetail failed", zap.Any("error", err))
		c.JSON(http.StatusOK, model.Fail("未找到指定员工信息"))
		return
	}

	if employee != nil {
		idDeptMap := dao.QueryDepartmentMap()
		deptInfo, has := idDeptMap[employee.DeptId]
		if has {
			if deptInfo != nil {
				employee.LeaderId = deptInfo.DeptHeadId
			}
		} else {
			global.Log.Error("QueryEmployeeDetail dept not exist ", zap.Any("DeptId", employee.DeptId))
			employee.LeaderId = 0
			employee.DeptId = 0
		}
	} else {
		global.Log.Info("QueryEmployeeDetail user info is null")
		c.JSON(http.StatusOK, model.Fail("未找到指定员工信息"))
		return
	}

	employee.Password = ""
	outout := &entity.EmployeeInfo{
		Id:        strconv.FormatInt(employee.Id, 10),
		Name:      employee.Name,
		Mobile:    employee.Mobile,
		Email:     employee.Email,
		DeptId:    strconv.FormatInt(employee.DeptId, 10),
		LeaderId:  strconv.FormatInt(employee.LeaderId, 10), //主管领导ID（必填）
		CreatedAt: employee.CreatedAt,
		UpdateAt:  employee.UpdateAt,
		Status:    employee.Status,
		Role:      employee.Role,
		Vendor:    employee.Vendor,
		WorkStart: employee.WorkStart, //上班时间
		WorkEnd:   employee.WorkEnd,   //下班时间
	}
	global.Log.Info("QueryEmployeeDetail user info:", zap.Any("employee:", employee))
	c.JSON(http.StatusOK, model.SuccessObject("操作成功", outout))
}

//********************************************
//Func  ：添加注册新员工
//Method：POST
//Usage ：用户注册-提交注册信息
//********************************************
func AddNewEmployee(c *gin.Context) {
	var err error
	var employee entity.Employee

	//todo. 此处需要判别当前请求发起者身份，仅HR才能添加人员
	requester := c.MustGet("USER").(*entity.Employee) //请求人
	global.Log.Info("AddNewEmployee() start @ ", zap.Any("USER", requester))

	if requester.Role != entity.EEmployeeRole_Hr {
		global.Log.Error("AddNewEmployee role not hr.")
		c.JSON(http.StatusOK, model.Fail("仅Hr才能进行增员操作"))
		return
	}

	employee.Name = strings.TrimSpace(c.DefaultPostForm("name", ""))
	if len(employee.Name) <= 0 {
		c.JSON(http.StatusOK, model.Fail("姓名不能为空"))
		return
	}

	role := c.DefaultPostForm("role", "") //如果是外包雇员，一定要有领导和部门，如果是部门领导或hr，可以暂时不填写
	if len(role) <= 0 {
		c.JSON(http.StatusOK, model.Fail("未指明员工角色"))
		return
	}
	employee.Role, err = strconv.Atoi(role)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("解析员工角色异常"))
		return
	}
	if employee.Role < entity.EEmployeeRole_Outsourcing || employee.Role > entity.EEmployeeRole_Hr {
		c.JSON(http.StatusOK, model.Fail("未支持的员工角色"))
		return
	}

	employee.LeaderId, err = strconv.ParseInt(c.DefaultPostForm("leaderId", ""), 10, 64)
	if err != nil && employee.Role == entity.EEmployeeRole_Outsourcing {
		//外包员工角色，一定需要指定部门和部门领导，此时部门和部门领导应该已经配置就位了。
		c.JSON(http.StatusOK, model.Fail("未指明直属主管"))
		return
	}
	if employee.LeaderId <= 0 && employee.Role == entity.EEmployeeRole_Outsourcing {
		//外包员工角色，一定需要指定部门和部门领导，此时部门和部门领导应该已经配置就位了。
		c.JSON(http.StatusOK, model.Fail("未指明直属主管"))
		return
	}

	employee.DeptId, err = strconv.ParseInt(c.DefaultPostForm("deptId", ""), 10, 64)
	if err != nil && employee.Role == entity.EEmployeeRole_Outsourcing {
		//外包员工角色，一定需要指定部门和部门领导，此时部门和部门领导应该已经配置就位了。
		c.JSON(http.StatusOK, model.Fail("未指明所属部门"))
		return
	}
	if employee.DeptId <= 0 && employee.Role == entity.EEmployeeRole_Outsourcing {
		//外包员工角色，一定需要指定部门和部门领导，此时部门和部门领导应该已经配置就位了。
		c.JSON(http.StatusOK, model.Fail("未指明所属部门"))
		return
	}

	employee.Email = c.DefaultPostForm("email", "")
	employee.Mobile = strings.TrimSpace(c.DefaultPostForm("mobile", ""))
	if len(employee.Mobile) <= 0 {
		c.JSON(http.StatusOK, model.Fail("手机号码不能为空"))
		return
	}

	employee.Password = c.DefaultPostForm("password", "")
	if len(employee.Password) <= 0 { //如果密码为空，默认使用手机号作为缺省密码
		employee.Password = employee.Mobile
		cryptoInstance2, ok := global.Crypto.Digest[crypto.CryptoSM3]
		if ok {
			employee.Password, err = cryptoInstance2.Sum([]byte(employee.Password))
			if err != nil {
				global.Log.Error("global.Crypto.Digest CryptoSM3 Sum error.", zap.Any("error", err))
			}
			//此处Password已经变更为密码摘要
		} else {
			global.Log.Error("global.Crypto.Digest CryptoSM3 error,no found")
		}
	} else {
		//此时密码需要解密，并生成摘要保存到Password字段.
		cryptoInstance, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
		if ok {
			pwdByte, err := cryptoInstance.DecryptWithBase64(employee.Password, global.ASymmetricPrivateKey)
			if err != nil {
				employee.Password = string(pwdByte)
				//此处Password已经变更为明文
			}
		} else {
			global.Log.Error("global.Crypto.Digest CryptoSM2 error,no found")
		}
		cryptoInstance2, ok := global.Crypto.Digest[crypto.CryptoSM3]
		if ok {
			employee.Password, err = cryptoInstance2.Sum([]byte(employee.Password))
			if err != nil {
				global.Log.Error("global.Crypto.Digest CryptoSM3 Sum error.", zap.Any("error", err))
			}
			//此处Password已经变更为密码摘要
		} else {
			global.Log.Error("global.Crypto.Digest CryptoSM3 error,no found")
		}
	}

	employee.Vendor = strings.TrimSpace(c.DefaultPostForm("vendor", "")) //厂商信息
	if employee.Role == entity.EEmployeeRole_Outsourcing {
		if len(employee.Vendor) <= 0 {
			c.JSON(http.StatusOK, model.Fail("未指明厂商信息"))
			return
		}
	}
	employee.WorkStart = strings.TrimSpace(c.DefaultPostForm("work_start", ""))
	if len(employee.WorkStart) <= 0 {
		c.JSON(http.StatusOK, model.Fail("上班时间不能为空"))
		return
	}
	employee.WorkEnd = strings.TrimSpace(c.DefaultPostForm("work_end", ""))
	if len(employee.WorkEnd) <= 0 {
		c.JSON(http.StatusOK, model.Fail("下班时间不能为空"))
		return
	}

	if employee.DeptId > 0 {
		idDeptMap := dao.QueryDepartmentMap()
		deptInfo, has := idDeptMap[employee.DeptId]
		if has {
			if deptInfo != nil {
				employee.LeaderId = deptInfo.DeptHeadId
			}
		} else {
			employee.LeaderId = 0
			employee.DeptId = 0
			global.Log.Error("AddNewEmployee dept not exist ", zap.Any("DeptId", employee.DeptId))
		}
	}

	global.Log.Info("AddNewEmployee: pareper to insert", zap.Any("employee:", employee))
	newer, err := dao.InsertEmployee(employee)
	if err != nil {
		if strings.Contains(err.Error(), "Duplicate") {
			c.JSON(http.StatusOK, model.Fail("手机号码已被占用"))
		} else {
			c.JSON(http.StatusOK, model.Fail("操作失败"))
		}
		global.Log.Info("AddNewEmployee InsertEmployee failed.", zap.Any("error", err))
	} else {
		if newer == nil {
			global.Log.Info("AddNewEmployee InsertEmployee return nil.")
			c.JSON(http.StatusOK, model.Fail("操作失败"))
			return
		}
		newerId := strconv.FormatInt(newer.Id, 10)
		c.JSON(http.StatusOK, model.SuccessObject("操作成功", newerId)) //返回新增用户的id
		global.Log.Info("AddNewEmployee success.", zap.Any("new employee", newer))
	}
}

//********************************************
//Func  ：更新用户信息
//Method：POST
//Usage ：用户注册-信息变更
//********************************************
func UpdateEmployee(c *gin.Context) {
	var err error
	var employee entity.Employee
	requester := c.MustGet("USER").(*entity.Employee) //请求人
	global.Log.Info("UpdateEmployee() start @ ", zap.Any("USER", requester))

	employee.Id, err = strconv.ParseInt(c.DefaultPostForm("id", ""), 10, 64)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("未指明用户ID"))
		return
	}

	employee.Name = strings.TrimSpace(c.DefaultPostForm("name", ""))
	if len(employee.Name) <= 0 {
		c.JSON(http.StatusOK, model.Fail("姓名不能为空"))
		return
	}

	employee.Mobile = strings.TrimSpace(c.DefaultPostForm("mobile", ""))
	if len(strings.TrimSpace(employee.Mobile)) <= 0 {
		c.JSON(http.StatusOK, model.Fail("手机号码不能为空"))
		return
	}

	role := c.DefaultPostForm("role", "") //如果是外包雇员，一定要有领导和部门，如果是部门领导或hr，可以暂时不填写
	if len(role) <= 0 {
		c.JSON(http.StatusOK, model.Fail("未指明员工角色"))
		return
	}
	employee.Role, err = strconv.Atoi(role)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("解析员工角色异常"))
		return
	}
	if employee.Role < entity.EEmployeeRole_Outsourcing || employee.Role > entity.EEmployeeRole_Hr {
		c.JSON(http.StatusOK, model.Fail("未支持的员工角色"))
		return
	}

	employee.LeaderId, err = strconv.ParseInt(c.DefaultPostForm("leaderId", ""), 10, 64)
	employee.DeptId, err = strconv.ParseInt(c.DefaultPostForm("deptId", ""), 10, 64)
	if employee.Role == entity.EEmployeeRole_Outsourcing {
		if employee.LeaderId <= 0 {
			c.JSON(http.StatusOK, model.Fail("未指明直属主管"))
			return
		}
		if employee.DeptId <= 0 {
			c.JSON(http.StatusOK, model.Fail("未指明所属部门"))
			return
		}
	}

	employee.Email = c.DefaultPostForm("email", "")
	employee.Vendor = strings.TrimSpace(c.DefaultPostForm("vendor", "")) //厂商信息
	if employee.Role == entity.EEmployeeRole_Outsourcing {
		if len(employee.Vendor) <= 0 {
			c.JSON(http.StatusOK, model.Fail("未指明厂商信息"))
			return
		}
	}
	employee.WorkStart = strings.TrimSpace(c.DefaultPostForm("work_start", ""))
	if len(employee.WorkStart) <= 0 {
		c.JSON(http.StatusOK, model.Fail("上班时间不能为空"))
		return
	}
	employee.WorkEnd = strings.TrimSpace(c.DefaultPostForm("work_end", ""))
	if len(employee.WorkEnd) <= 0 {
		c.JSON(http.StatusOK, model.Fail("下班时间不能为空"))
		return
	}

	global.Log.Info("UpdateEmployee: pareper to update", zap.Any("customer:", employee))

	err = dao.UpdateEmployee(employee, "")
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("操作失败"))
		global.Log.Info("UpdateEmployee UpdateEmployee failed.", zap.Any("error", err))
	} else {
		idDeptMap := dao.QueryDepartmentMap()
		if employee.DeptId > 0 {
			deptInfo, has := idDeptMap[employee.DeptId]
			if has {
				if deptInfo != nil {
					employee.LeaderId = deptInfo.DeptHeadId
				}
			} else {
				employee.LeaderId = 0
				employee.DeptId = 0
				global.Log.Error("UpdateEmployee dept not exist ", zap.Any("DeptId", employee.DeptId))
			}
		}

		outout := &entity.EmployeeInfo{
			Id:        strconv.FormatInt(employee.Id, 10),
			Name:      employee.Name,
			Mobile:    employee.Mobile,
			Email:     employee.Email,
			DeptId:    strconv.FormatInt(employee.DeptId, 10),
			LeaderId:  strconv.FormatInt(employee.LeaderId, 10), //主管领导ID（必填）
			CreatedAt: employee.CreatedAt,
			UpdateAt:  employee.UpdateAt,
			Status:    employee.Status,
			Role:      employee.Role,
			Vendor:    employee.Vendor,
			WorkStart: employee.WorkStart, //上班时间
			WorkEnd:   employee.WorkEnd,   //下班时间
		}

		c.JSON(http.StatusOK, model.Success("更新成功"))
		global.Log.Info("UpdateCustomer success.", zap.Any("new employee", outout))
	}
}

//********************************************
//Func  ：用户删除
//Method：DELETE
//Usage ：留言板页面-个人设置-账号注销
//********************************************
func DeleteEmployee(c *gin.Context) {

	requester := c.MustGet("USER").(*entity.Employee) //请求人
	global.Log.Info("DeleteEmployee() start @ ", zap.Any("USER", requester))

	if requester.Role != entity.EEmployeeRole_Hr {
		global.Log.Error("DeleteEmployee role not hr.")
		c.JSON(http.StatusOK, model.Fail("仅Hr才能进行减员操作"))
		return
	}

	customerIdStr := c.DefaultQuery("id", "")
	mobile := c.DefaultQuery("mobile", "")
	if customerIdStr == "" && mobile == "" {
		global.Log.Error("DeleteEmployee delete failed")
		c.JSON(http.StatusOK, model.Fail("未指定用户信息"))
		return
	}

	customerId, err := strconv.ParseInt(customerIdStr, 10, 64)
	if err != nil {
		customerId = -1
	}
	err = dao.DeleteEmployee(mobile, customerId)
	if err != nil {
		global.Log.Error("DeleteEmployee failed", zap.Any("mobile", mobile), zap.Any("id", customerId), zap.Any("error", err))
		c.JSON(http.StatusOK, model.Fail("移除用户失败, "+err.Error()))
	} else {
		global.Log.Info("DeleteEmployee success", zap.Any("mobile", mobile), zap.Any("id", customerId), zap.Any("error", err))
		c.JSON(http.StatusOK, model.Success("删除成功"))
	}
}

//********************************************
//Func  ：绑定手机号到用户-校验密码
//Method：POST
//Usage ：绑定手机-校验密码
//********************************************
func BindMobileToEmployeeCheck(c *gin.Context) {
	customer := c.MustGet("USER").(*entity.Employee)
	global.Log.Info("BindMobileToEmployeeCheck() start @ ", zap.Any("USER", customer))

	req := entity.ReqBindMobileCustomerCheck{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("BindMobileToEmployeeCheck ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusBadRequest, model.Fail("参数错误"))
		return
	}
	// 解密密码密文
	cryptoAsymmetric, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("BindMobileToEmployeeCheck invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	passwordByte, err := cryptoAsymmetric.DecryptWithBase64(req.Password, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("BindMobileToEmployeeCheck sm2 decrypt password fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("密码无效"))
		return
	}
	// 校验密码是否正确
	cryptoDigest, ok := global.Crypto.Digest[crypto.CryptoSM3]
	if !ok {
		global.Log.Error("BindMobileToEmployeeCheck invalid crypto", zap.Any("type", crypto.CryptoSM3))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	passwordSum, err := cryptoDigest.Sum(passwordByte)
	if err != nil {
		global.Log.Error("BindMobileToEmployeeCheck sm3 sum fail", zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	if passwordSum != customer.Password {
		global.Log.Error("BindMobileToEmployeeCheck invalid password")
		global.Log.Debug("BindMobileToEmployeeCheck invalid password", zap.Any("password", req.Password))
		c.JSON(http.StatusUnauthorized, model.Fail("无效密码"))
		return
	}
	// 生成凭证，用于绑定是校验
	rsp := entity.RspBindMobileCustomerCheck{
		Evidence: uuid.NewV4().String(),
	}
	// 缓存凭证
	key := entity.RedisCustomerBindMobileEvidence + strconv.FormatInt(customer.Id, 10)

	if _, err = cache.TransBegin().
		Send("SET", key, rsp.Evidence).
		Send("EXPIRE", key, 60).Exec(); err != nil {
		global.Log.Error("BindMobileToEmployeeCheck set fail",
			zap.Any("key", key),
			zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	c.JSON(http.StatusOK, model.SuccessObject("校验通过", rsp))
	return
}

//********************************************
//Func  ：绑定手机号到用户
//Method：POST
//Usage ：留言板页面-个人设置-绑定手机
//********************************************
func BindMobileToEmployee(c *gin.Context) {
	customer := c.MustGet("USER").(*entity.Employee)
	global.Log.Info("BindMobileToEmployee() start @ ", zap.Any("USER", customer))

	req := entity.ReqBindMobileCustomer{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("BindMobileToEmployee ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusBadRequest, model.Fail("参数错误"))
		return
	}
	// 解密密码密文
	cryptoAsymmetric, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("BindMobileToEmployee invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	mobileByte, err := cryptoAsymmetric.DecryptWithBase64(req.Mobile, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("BindMobileToEmployee sm2 decrypt mobile fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("手机号无效"))
		return
	}
	req.Mobile = string(mobileByte)
	// 校验验证码
	if err = global.MsgCodeGenerator.Check(req.Mobile, req.Code); err != nil {
		global.Log.Error("BindMobileToEmployee invalid code", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail(err.Error()))
		return
	}
	// 校验凭证
	key := entity.RedisCustomerBindMobileEvidence + strconv.FormatInt(customer.Id, 10)
	evidence, err := redis.String(cache.Do("GET", key))
	if err != nil {
		global.Log.Error("BindMobileToEmployee get fail",
			zap.Any("key", key),
			zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	if req.Evidence != evidence {
		global.Log.Error("BindMobileToEmployee ivalid evidence", zap.Any("evidence", req.Evidence))
		c.JSON(http.StatusBadRequest, model.Fail("凭证无效"))
		return
	}
	// 删除凭证
	_, err = cache.Do("DEL", key)
	if err != nil {
		global.Log.Error("BindMobileToEmployee del fail",
			zap.Any("key", key),
			zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	// 更新手机号
	err = dao.UpdateEmployeeMobile("", customer.Id, req.Mobile)
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("操作失败"))
		return
	} else {
		c.JSON(http.StatusOK, model.Success("操作成功"))
		return
	}
}

//********************************************
//Func  ：用户修改密码
//Method：POST
//        手机号（密文）
//        用户id（密文）
//        新密码（密文）
//Usage ：留言板页面-个人设置-密码设置
//********************************************
func EmployeeChangePassword(c *gin.Context) {
	customer := c.MustGet("USER").(*entity.Employee)
	global.Log.Info("EmployeeChangePassword() start @ ", zap.Any("USER", customer))

	req := entity.ReqChangePasswordCustomer{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("EmployeeChangePassword ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusBadRequest, model.Fail("参数错误"))
		return
	}
	// 解密密码密文
	cryptoAsymmetric, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("EmployeeChangePassword invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	oldByte, err := cryptoAsymmetric.DecryptWithBase64(req.OldPassword, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("EmployeeChangePassword sm2 decrypt oldpassword fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("旧密码无效"))
		return
	}
	newByte, err := cryptoAsymmetric.DecryptWithBase64(req.NewPassword, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("EmployeeChangePassword sm2 decrypt newpassword fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("新密码无效"))
		return
	}
	// 校验旧密码是否正确
	cryptoDigest, ok := global.Crypto.Digest[crypto.CryptoSM3]
	if !ok {
		global.Log.Error("EmployeeChangePassword invalid crypto", zap.Any("type", crypto.CryptoSM3))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	oldpwdSum, err := cryptoDigest.Sum(oldByte)
	if err != nil {
		global.Log.Error("EmployeeChangePassword sm3 sum fail", zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	if oldpwdSum != customer.Password {
		global.Log.Error("EmployeeChangePassword invalid oldPassword")
		c.JSON(http.StatusUnauthorized, model.Fail("无效旧密码"))
		return
	}
	// 校验新密码强度
	if !util.IsStrongPassword(string(newByte)) {
		c.JSON(http.StatusBadRequest, model.Fail("新密码强度不足"))
		return
	}
	// 新密码摘要
	pwdSum, err := cryptoDigest.Sum(newByte)
	if err != nil {
		global.Log.Error("EmployeeChangePassword sm3 sum fail", zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	// 更新密码
	err = dao.UpdateEmployeePassword("", customer.Id, pwdSum)
	if err != nil {
		c.JSON(http.StatusInternalServerError, model.Fail("操作失败"))
		return
	} else {
		c.JSON(http.StatusOK, model.Success("密码更新成功"))
		return
	}
}

//********************************************
//Func  ：用户找回密码（使用手机）,向手机号发送短信验证码，返回用户id（密文）。
//       下一步配合CustomerChangePassword接口修改系密码
//Method：POST
//Param ：设备号或唯一标识（密文）
//        手机号（密文）
//        图形验证码
//Usage ：
//********************************************
func RetrieveEmployeePasswordMsgVerCode(c *gin.Context) {
	customer := c.MustGet("USER").(*entity.Customer)
	global.Log.Info("RetrieveEmployeePasswordMsgVerCode() start @ ", zap.Any("USER", customer))

	req := entity.ReqRetrievePasswordMsgVerCodeCustomer{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusBadRequest, model.Fail("参数错误"))
		return
	}
	// 解密密码密文
	cryptoAsymmetric, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	passwordByte, err := cryptoAsymmetric.DecryptWithBase64(req.Password, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode sm2 decrypt password fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("密码无效"))
		return
	}
	mobileByte, err := cryptoAsymmetric.DecryptWithBase64(req.Mobile, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode sm2 decrypt mobile fail", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail("手机号无效"))
		return
	}
	req.Mobile = string(mobileByte)
	// 校验手机号（如果已绑定）
	if len(customer.Mobile) > 0 && req.Mobile != customer.Mobile {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode invalid mobile fail", zap.Any("db", customer.Mobile), zap.Any("req", req.Mobile))
		c.JSON(http.StatusBadRequest, model.Fail("手机号无效"))
		return
	}
	// 校验短信验证码
	if err := global.MsgCodeGenerator.Check(req.Mobile, req.Code); err != nil {
		global.Log.Error("RetrieveEmployeePasswordMsgVerCode MsgCodeGenerator.Check fail,", zap.Any("error", err))
		c.JSON(http.StatusBadRequest, model.Fail(err.Error()))
		return
	}

	// 校验密码强度
	if !util.IsStrongPassword(string(passwordByte)) {
		c.JSON(http.StatusOK, model.Fail("密码强度不足"))
		return
	}
	// 密码摘要
	cryptoDigest, ok := global.Crypto.Digest[crypto.CryptoSM3]
	if !ok {
		global.Log.Error("EmployeeChangePassword invalid crypto", zap.Any("type", crypto.CryptoSM3))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	passwordSum, err := cryptoDigest.Sum(passwordByte)
	if err != nil {
		global.Log.Error("EmployeeChangePassword sm3 sum fail", zap.Any("error", err))
		c.JSON(http.StatusInternalServerError, model.Fail("内部错误"))
		return
	}
	// 更细密码
	err = dao.UpdateEmployeePassword("", customer.Id, string(passwordSum))
	if err != nil {
		c.JSON(http.StatusOK, model.Fail("操作失败"))
		return
	} else {
		c.JSON(http.StatusOK, model.Success("密码重置成功"))
		return
	}
}

//********************************************
//Func  ：用户短信验证码登录
//Method：POST
//Usage ：留言板页面-用户注册-信息变更
//********************************************
func LoginSmsEmployee(c *gin.Context) {
	req := entity.ReqSmsLoginCustomer{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("LoginSmsEmployee ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusOK, model.Fail("参数错误"))
		return
	}
	// 解密手机号码
	cryptoInst, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("LoginSmsEmployee invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}

	if len(req.Mobile) > 11 {
		mobileByte, err := cryptoInst.DecryptWithBase64(req.Mobile, global.ASymmetricPrivateKey)
		if err != nil {
			global.Log.Error("LoginSmsEmployee invalid sm2.Decrypt", zap.Any("err", err))
			c.JSON(http.StatusOK, model.Fail("内部错误"))
			return
		}
		req.Mobile = string(mobileByte)
	}

	global.Log.Info("LoginSmsEmployee() start @ ", zap.Any("Mobile", req.Mobile))
	// 确认用户已注册
	customer, err := dao.QueryEmployeeDetail(req.Mobile, 0)
	if err != nil || nil == customer {
		global.Log.Error("LoginSmsEmployee no found this custom. err msg is "+err.Error(), zap.Any("mobile", req.Mobile))
		c.JSON(http.StatusOK, model.Fail("未找到该用户信息"))
		return
	}
	// 校验验证码
	if err = global.MsgCodeGenerator.Check(req.Mobile, req.Code); err != nil {
		global.Log.Error("LoginSmsEmployee MsgCodeGenerator.Check fail,", zap.Any("err", err), zap.Any("mobile", req.Mobile))
		c.JSON(http.StatusOK, model.Fail(err.Error()))
		return
	}
	// 生成token
	token, err := util.GetJwtToken(customer.Id, config.Token().Expire, config.Token().Key)
	if err != nil {
		global.Log.Error("LoginSmsEmployee GetJwtToken failed.", zap.Any("err", err), zap.Any("mobile", req.Mobile))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	log := entity.CustomerLoginLog{
		Id:         util.GenSnowflakeInt64(),
		CustomerId: customer.Id,
		LoginTime:  time.Now(),
		LoginType:  entity.LoginType_MobileVercode,
		Token:      token,
	}
	err = dao.InsertCustmoerLoginLog(log)
	if err != nil {
		global.Log.Warn("LoginSmsEmployee.InsertCustmoerLoginLog failed.", zap.Any("err", err), zap.Any("mobile", req.Mobile))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}

	type RspLoginEmployee struct { //继承自RspLoginCustomer。补充role字段
		Token  string `form:"token" json:"token"`   // token
		Expire int    `form:"expire" json:"expire"` // 过期时间
		Role   int    `form:"role" json:"role"`     //角色
		ID     string //int64
		Mobile string
		Email  string
		DeptID string //int64
		Name   string
	}

	rsp := RspLoginEmployee{
		Token:  token,
		Expire: config.Token().Expire,
		Role:   customer.Role,
		ID:     strconv.FormatInt(customer.Id, 10),
		Mobile: customer.Mobile,
		Email:  customer.Email,
		DeptID: strconv.FormatInt(customer.DeptId, 10),
		Name:   customer.Name,
	}
	c.JSON(http.StatusOK, model.SuccessObject("登录成功", rsp))
	global.Log.Info("LoginSmsEmployee success.", zap.Any("token", rsp))
}

//********************************************
//Func  ：用户登录--手机+密码
//Method：POST
//Usage ：
//********************************************
func LoginEmployee(c *gin.Context) {
	req := entity.ReqPwdLoginCustomer{}
	if err := c.ShouldBind(&req); err != nil {
		global.Log.Error("LoginEmployee ShouldBind fail", zap.Any("err", err))
		c.JSON(http.StatusBadRequest, model.Fail("参数错误"))
		return
	}
	// 解密手机号码
	cryptoAsymmetric, ok := global.Crypto.Asymmetric[crypto.CryptoSM2]
	if !ok {
		global.Log.Error("LoginEmployee invalid crypto", zap.Any("type", crypto.CryptoSM2))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	mobileByte, err := cryptoAsymmetric.DecryptWithBase64(req.Mobile, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("LoginEmployee invalid sm2.Decrypt", zap.Any("err", err))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	req.Mobile = string(mobileByte)
	// 解密密码
	passwordByte, err := cryptoAsymmetric.DecryptWithBase64(req.Password, global.ASymmetricPrivateKey)
	if err != nil {
		global.Log.Error("LoginEmployee invalid sm2.Decrypt", zap.Any("err", err))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	global.Log.Info("LoginEmployee() start @ ", zap.Any("Mobile", req.Mobile), zap.Any("Password", string(passwordByte)))
	// 获取密码摘要
	cryptoDigest, ok := global.Crypto.Digest[crypto.CryptoSM3]
	if !ok {
		global.Log.Error("LoginEmployee invalid crypto", zap.Any("type", crypto.CryptoSM3))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	passwordDigest, err := cryptoDigest.Sum(passwordByte)
	if err != nil {
		global.Log.Error("LoginEmployee invalid sm3.Sum", zap.Any("err", err))
		c.JSON(http.StatusOK, model.Fail("内部错误"))
		return
	}
	req.Password = string(passwordDigest)
	// 校验密码
	customer, err := dao.QueryEmployeeDetail(req.Mobile, 0)
	if err != nil {
		global.Log.Error("LoginEmployee no found this custom.")
		c.JSON(http.StatusOK, model.Fail("未找到该用户信息"))
		return
	}
	if req.Password != customer.Password {
		global.Log.Error("LoginEmployee invalid password")
		global.Log.Debug("LoginEmployee invalid password", zap.Any("password", req.Password))
		c.JSON(http.StatusOK, model.Fail("密码错误"))
		return
	}
	// 生成token
	token, err := util.GetJwtToken(customer.Id, config.Token().Expire, config.Token().Key)
	if err != nil {
		global.Log.Error("LoginEmployee GetJwtToken failed.", zap.Any("error", err))
	}
	// 记录login_log
	log := entity.CustomerLoginLog{
		Id:         util.GenSnowflakeInt64(),
		CustomerId: customer.Id,
		LoginTime:  time.Now(),
		LoginType:  entity.LoginType_MobilePwd,
		Token:      token,
	}
	if err = dao.InsertCustmoerLoginLog(log); err != nil {
		global.Log.Error("LoginEmployee InsertCustmoerLoginLog failed.", zap.Any("error", err))
		c.JSON(http.StatusOK, model.Fail("登陆失败"))
		return
	}
	type RspLoginEmployee struct { //继承自RspLoginCustomer。补充role字段
		Token  string `form:"token" json:"token"`   // token
		Expire int    `form:"expire" json:"expire"` // 过期时间
		Role   int    `form:"role" json:"role"`     //角色
		ID     string
		Mobile string
		Email  string
		DeptID string
	}

	rsp := RspLoginEmployee{
		Token:  token,
		Expire: config.Token().Expire,
		Role:   customer.Role,
		ID:     strconv.FormatInt(customer.Id, 10),
		Mobile: customer.Mobile,
		Email:  customer.Email,
		DeptID: strconv.FormatInt(customer.DeptId, 10),
	}
	c.JSON(http.StatusOK, model.SuccessObject("登录成功", rsp))
	global.Log.Info("LoginCustomer success.", zap.Any("new token", rsp))
}

//********************************************
//Func  ：用户登出
//Method：POST
//Usage ：留言板页面-用户注册-信息变更？？ 30天自动logout
//********************************************
func LogoutEmployee(c *gin.Context) {
	//todo ，del token吗
	var log entity.CustomerLoginLog
	mobile := strings.TrimSpace(c.DefaultPostForm("mobile", ""))
	if mobile == "" {
		global.Log.Error("LogoutEmployee mobile is none.")
		c.JSON(http.StatusOK, model.Fail("用户信息错误"))
		return
	}
	log.CustomerId, _ = strconv.ParseInt(c.DefaultPostForm("customerId", ""), 10, 64)
	log.LogoutTime = time.Now()
	log.LoginOrLogout = 1
	dao.InsertCustmoerLoginLog(log)
	c.JSON(http.StatusOK, model.SuccessObject("登出成功", ""))
}
